package redis

import (
	"errors"
	"github.com/gomodule/redigo/redis"
)

type cmd struct {
	// req
	c    *Client
	cmd  string
	args []any
	// resp
	reply any
	err   error
}

func cmdErr(err error) *cmd { return &cmd{err: err} }

func (c *cmd) exec() {
	if c.c == nil || c.cmd == "" {
		return
	}
	if c.reply != nil || c.err != nil { // executed
		return
	}
	conn := c.c.pool.Get()
	defer conn.Close()
	c.reply, c.err = conn.Do(c.cmd, c.args...)
}

var ErrNil = redis.ErrNil

func (c *cmd) Error() error {
	c.exec()
	if c.err != nil {
		return c.err
	}
	switch reply := c.reply.(type) {
	case nil:
		return ErrNil
	case redis.Error:
		return reply
	}
	return nil
}

func (c *cmd) Bool() (bool, error) {
	c.exec()
	switch reply := c.reply.(type) {
	case string:
		if reply == "OK" {
			return true, nil
		}
	}
	return redis.Bool(c.reply, c.err)
}

func (c *cmd) ByteSlices() ([][]byte, error) {
	c.exec()
	return redis.ByteSlices(c.reply, c.err)
}

func (c *cmd) Bytes() ([]byte, error) {
	c.exec()
	return redis.Bytes(c.reply, c.err)
}

func (c *cmd) Float64() (float64, error) {
	c.exec()
	return redis.Float64(c.reply, c.err)
}

func (c *cmd) Int() (int, error) {
	c.exec()
	return redis.Int(c.reply, c.err)
}

func (c *cmd) IntMap() (map[string]int, error) {
	c.exec()
	return redis.IntMap(c.reply, c.err)
}

func (c *cmd) Int64() (int64, error) {
	c.exec()
	return redis.Int64(c.reply, c.err)
}

func (c *cmd) Int64Map() (map[string]int64, error) {
	c.exec()
	return redis.Int64Map(c.reply, c.err)
}

func (c *cmd) Ints() ([]int, error) {
	c.exec()
	return redis.Ints(c.reply, c.err)
}

func (c *cmd) Int64s() ([]int64, error) {
	c.exec()
	return redis.Int64s(c.reply, c.err)
}

func (c *cmd) String() (string, error) {
	c.exec()
	return redis.String(c.reply, c.err)
}

func (c *cmd) Strings() ([]string, error) {
	c.exec()
	return redis.Strings(c.reply, c.err)
}

func (c *cmd) StringMap() (map[string]string, error) {
	c.exec()
	return redis.StringMap(c.reply, c.err)
}

func (c *cmd) Values() ([]any, error) {
	c.exec()
	return redis.Values(c.reply, c.err)
}

type Result interface {
	Error() error
	Bool() (bool, error)
	ByteSlices() ([][]byte, error)
	Bytes() ([]byte, error)
	Float64() (float64, error)
	Int() (int, error)
	IntMap() (map[string]int, error)
	Int64() (int64, error)
	Int64Map() (map[string]int64, error)
	Ints() ([]int, error)
	Int64s() ([]int64, error)
	String() (string, error)
	Strings() ([]string, error)
	StringMap() (map[string]string, error)
	Values() ([]any, error)
	ZRangeRows() ([]*ZRangeRow, error)
}

type ErrorResult interface {
	Error() error
}

type ValueResult interface {
	Error() error
	Bool() (bool, error)
	Bytes() ([]byte, error)
	Float64() (float64, error)
	Int() (int, error)
	Int64() (int64, error)
	String() (string, error)
}

type IntResult interface {
	Error() error
	Int() (int, error)
	Int64() (int64, error)
}

type FloatResult interface {
	Error() error
	Float64() (float64, error)
}

type StringResult interface {
	Error() error
	String() (string, error)
	Bytes() ([]byte, error)
}

type BoolResult interface {
	Error() error
	Bool() (bool, error)
}

type ListResult interface {
	Error() error
	ByteSlices() ([][]byte, error)
	Ints() ([]int, error)
	Int64s() ([]int64, error)
	Strings() ([]string, error)
	Values() ([]any, error)
}

type StringListResult interface {
	Error() error
	ByteSlices() ([][]byte, error)
	Strings() ([]string, error)
}

type IntListResult interface {
	Error() error
	Ints() ([]int, error)
	Int64s() ([]int64, error)
}

type MapResult interface {
	Error() error
	IntMap() (map[string]int, error)
	Int64Map() (map[string]int64, error)
	StringMap() (map[string]string, error)
}

type ZRangeRow struct {
	Score  int64
	Member ValueResult
}

func (c *cmd) ZRangeRows() ([]*ZRangeRow, error) {
	values, err := c.Values()
	if err != nil {
		return nil, err
	}
	if len(values)%2 != 0 {
		return nil, errors.New("ZRangeRows expects even number of values cmd")
	}
	var list []*ZRangeRow
	for i := 0; i < len(values); i += 2 {
		score, _ := redis.Int64(values[i+1], nil)
		list = append(list, &ZRangeRow{
			Score:  score,
			Member: &cmd{reply: values[i]},
		})
	}
	return list, nil
}

type ZRangeResult interface {
	Error() error
	ZRangeRows() ([]*ZRangeRow, error)
}
